//! Rust 入口定义

use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::{parse, parse_macro_input, spanned::Spanned, ItemFn, ReturnType, Type, Visibility};

/// 内核主程序 rust 入口
///
/// # Panics
/// io 错误将会 panic
#[proc_macro_attribute]
pub fn main_entry(args: TokenStream, input: TokenStream) -> TokenStream {
    let f = parse_macro_input!(input as ItemFn);

    // check the function signature
    let valid_signature = f.sig.constness.is_none()
        && f.vis == Visibility::Inherited
        && f.sig.abi.is_none()
        && f.sig.inputs.is_empty()
        && f.sig.generics.params.is_empty()
        && f.sig.generics.where_clause.is_none()
        && f.sig.variadic.is_none()
        && match f.sig.output {
            ReturnType::Default => false,
            ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
        };

    if !valid_signature {
        return parse::Error::new(
            f.span(),
            "`#[main_entry]` function must have signature `[unsafe] fn() -> !`",
        )
        .to_compile_error()
        .into();
    }

    if !args.is_empty() {
        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
            .to_compile_error()
            .into();
    }

    let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
    let ident = &f.sig.ident;

    let asm0 = include_str!("aarch64/define.S");
    let asm1 = include_str!("aarch64/macro.S");
    let asm2 = include_str!("aarch64/head.S");
    let asm3 = include_str!("aarch64/cache.S");
    let asm4 = include_str!("aarch64/entry.S");
    let asm5 = include_str!("aarch64/clear_page.S");

    quote!(
        core::arch::global_asm!(#asm0);
        core::arch::global_asm!(#asm1);
        core::arch::global_asm!(#asm2);
        core::arch::global_asm!(#asm3);
        core::arch::global_asm!(#asm4);
        core::arch::global_asm!(#asm5);

        extern "Rust" {
            fn __main_setup_early(f: fn() -> !);
        }

        #[doc(hidden)]
        #[export_name = "__ffi_start_kernel"]
        pub unsafe extern "C" fn #tramp_ident() {
            __main_setup_early(#ident);
        }

        #f
    )
    .into()
}
